package com.xzzz.irda.auth.server.authentication;

import cn.hutool.core.util.StrUtil;
import com.xzzz.irda.auth.core.authorization.Authorization;
import com.xzzz.irda.auth.core.constants.GrantTypeEnum;
import com.xzzz.irda.auth.core.AuthBaseProperties;
import com.xzzz.irda.auth.core.token.AccessToken;
import com.xzzz.irda.auth.core.token.AuthorizationRepository;
import com.xzzz.irda.auth.core.util.TokenUtil;
import com.xzzz.irda.auth.server.AuthServerConfiguration;
import com.xzzz.irda.auth.server.pojo.LoginMetadata;
import com.xzzz.irda.auth.server.service.UserDetail;
import com.xzzz.irda.auth.core.token.DefaultAuthorizationRepositoryRedis;
import com.xzzz.irda.auth.server.service.UserDetailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.util.Date;

/**
 * 授权方法的抽象类
 * <p>
 * 用户实现公共方法, 并实现模板模式 {@link this#build(Authorization, LoginMetadata)}
 *
 * @author wangyf
 * 
 */
@Slf4j
public abstract class AbstractAuthenticationManager implements AuthenticationManager {

    /**
     * 服务端配置
     */
    protected AuthServerConfiguration serverConfig;

    protected AuthBaseProperties properties;

    // region 构造方法

    /**
     * 授权实现类的授权方式配置
     *
     * @return 授权方式枚举
     */
    @Override
    public String getGrantType() {
        return null;
    }

    public AbstractAuthenticationManager() {
    }


    /**
     * 构造对象
     *
     * @param serverConfig 服务端配置
     * @param properties   配置文件内容
     */
    public AbstractAuthenticationManager(AuthServerConfiguration serverConfig, AuthBaseProperties properties) {
        this.serverConfig = serverConfig;
        this.properties = properties;
    }

    // endregion

    /**
     * 模板方法设计模式, 构造授权主体的全部信息
     * <p>
     * 授权对象的构建
     * <p>
     * 1. 「子类实现」授权信息的前置处理{@link AuthenticationManager#authorizationBeforeProcess}, 通常是将请求参数中获取该授权方
     * 式所需要的字段和校验,如手机号登录则需要获取手机号,并创建特殊的授权对象类型, 实际为{@link Authorization}接口的各类实现,
     * 在{@link GrantTypeEnum}中进行授权方式和授权对象的绑定
     * <p>
     * 2. 「子类实现」的用户信息查询{@link AuthenticationManager#findUser}, 通过不同的查询方式
     * {@link UserDetailService}来查询用户,在判断用户是否允许登录(如密码匹配,
     * 手机验证码匹配)后将抽象对象{@link UserDetail}返回,由后续流程处理用户信息和授权对象的数据关系
     * <p>
     * 3. 「子类实现」授权查询后的处理{@link AuthenticationManager#authorizationAfterProcess}, 处理一些后置的处理, 如将用户的密码
     * 脱敏, 将验证码删除等操作
     * <p>
     * 4. 生成token {@link #tokenGenerate} 生成token对象
     * <p>
     * 5. 生成最终的授权主体{@link AbstractAuthenticationManager#authorizationGenerate(Authorization)}
     * 从配置文件中获取对应客户端的过期时间,并填充进授权主体对象中,如果平台允许多地登录,则每次返回的token相同,如果不允许则会生成新的
     * 授权对象,并将旧的删除
     * <p>
     * 6. 保存授权对象和 token 对象到对应存储介质中, 不同的实现类可达到不同方式存储token的逻辑, 例如存放至ES或SQL中
     * 由 {@link AuthorizationRepository} 实现
     * 默认实现类 {@link DefaultAuthorizationRepositoryRedis}
     *
     * @return 授权主体
     */
    @Override
    public Authorization build(Authorization authorization, LoginMetadata loginMetadata) {
        // 1. 子类实现, 授权判断前的处理
        authorizationBeforeProcess(authorization, loginMetadata);
        // 2. 子类实现, 生成 token
        UserDetail userDetail = findUser(authorization, loginMetadata);
        // 3. 子类实现, 授权判断后的处理
        authorizationAfterProcess(authorization, loginMetadata);
        // 4. 生成token
        tokenGenerate(authorization, userDetail);
        // 5. 生成授权主体
        Authorization effectiveAuthorization = authorizationGenerate(authorization);
        // 6. 保存授权主体
        saveAuthorization(effectiveAuthorization);
        return effectiveAuthorization;
    }


    /**
     * 1. 授权主体的业务信息填充, 需要子类实现
     *
     * @param authorization 授权对象
     * @param loginMetadata 请求参数
     * @return 包含业务信息的授权主体信息
     */
    @Override
    public Authorization authorizationBeforeProcess(Authorization authorization, LoginMetadata loginMetadata) {
        return null;
    }

    /**
     * 3. 授权对象后处理
     *
     * @param authorization 授权主体
     * @param loginMetadata 登录请求参数
     * @return 授权主体
     */
    @Override
    public Authorization authorizationAfterProcess(Authorization authorization, LoginMetadata loginMetadata) {
        return null;
    }

    /**
     * 4. 授权主体信息增强
     *
     * @return 返回最终有效的 effectiveAuthorization
     */
    private Authorization authorizationGenerate(Authorization authorization) {
        AuthBaseProperties.Client clientConfig = getAuthorizationFromClient(authorization);
        // 获取当前用户有效的 token
        String uniqueToken = getAuthRepo().getUniqueToken(TokenUtil.buildUniqueTokenKey(authorization));
        if (StrUtil.isNotBlank(uniqueToken)) {
            // 获取有效 token 对象
            Authorization uniqueAuth = getAuthorization(uniqueToken);
            if (uniqueAuth != null) {
                // 如果允许多地登录, 则返回相同的token信息
                if (clientConfig.getMultiPlaceLogin()) {
                    authorization = uniqueAuth;
                }
                // 如果不允许多地登陆, 则把旧token删除, 使用本次的新token
                else {
                    this.removeAuthorization(uniqueAuth);
                }
            }
        }

        // 授权过期倒数
        authorization.setExpire(clientConfig.getDuration());
        // 授权到期时间
        authorization.setExpireTime(System.currentTimeMillis() + (clientConfig.getDuration() * 1000));
        // 授权有效时长
        authorization.setDuration(clientConfig.getDuration());
        // 请求是否刷新授权,将刷新标识放在授权中,客户端则无需配置
        authorization.setRequestRefresh(clientConfig.getRequestRefresh());
        // 如果登录时间不为空,则赋值
        if (authorization.getLoginDateTime() == null) {
            authorization.setLoginDateTime(new Date());
        }

        // 如果没有token,则生成一个token,只有新的token才会没有token值,从effective中获取的token一定是有值的
        if (StrUtil.isBlank(authorization.getAccessToken().getToken())) {
            AccessToken accessToken = authorization.getAccessToken();
            accessToken.setToken(serverConfig.getTokenGenerator().generate(authorization));
            accessToken.setExpire(authorization.getDuration());
        }

        return authorization;
    }


    // region 授权主体管理

    /**
     * 6. 保存授权主体, 同时保存有效的授权信息
     *
     * @param authorization 授权主体
     */
    @Override
    public void saveAuthorization(Authorization authorization) {
        // 保存授权主体信息
        getAuthRepo().saveAuthorization(authorization);
        // 保存有效的授权信息
        getAuthRepo().saveUniqueToken(authorization);
    }

    @Override
    public void refreshAuthorization(String token) {
    }

    /**
     * 删除授权主体, 如果有效token也为该授权信息,则一并删除
     *
     * @param authorization 授权主体
     */
    @Override
    public void removeAuthorization(Authorization authorization) {
        if (authorization == null) {
            return;
        }
        removeAuthorization(authorization.getAccessToken().getToken());
    }

    /**
     * 删除授权主体, 如果有效token也为该授权信息,则一并删除
     *
     * @param key 授权主体的key
     */
    @Override
    public void removeAuthorization(String key) {
        // 获取被删除的授权信息
        Authorization authorization = getAuthRepo().getAuthorization(key);
        // 1. 删除授权信息
        getAuthRepo().removeAuthorization(key);

        // 2. 检查授权信息是否为有效授权信息
        String token = getAuthRepo().getUniqueToken(TokenUtil.buildUniqueTokenKey(authorization));

        // 3. 如果有效授权信息与删除的授权信息相同,则一并删除
        if (token.equals(authorization.getAccessToken().getToken())) {
            getAuthRepo().removeUniqueToken(TokenUtil.buildUniqueTokenKey(authorization));
        }
    }

    /**
     * 查询授权主体
     *
     * @param authorization 授权主体
     * @return 授权主体
     */
    @Override
    public Authorization getAuthorization(Authorization authorization) {
        return getAuthorization(authorization.getAccessToken().getToken());
    }

    /**
     * 查询授权主体
     *
     * @param token 授权主体key
     * @return 授权主体
     */
    @Override
    public Authorization getAuthorization(String token) {
        return getAuthRepo().getAuthorization(token);
    }

    // endregion

    // region 其他方法

    /**
     * 获取平台配置信息
     *
     * @return 平台配置信息
     */
    public AuthBaseProperties.Client getAuthorizationFromClient(Authorization authorization) {
        return properties.getClient(authorization.getClientId());
    }

    /**
     * 获取授权存储介质对象
     *
     * @return 授权存储介质对象
     */
    protected AuthorizationRepository getAuthRepo() {
        return serverConfig.getAuthorizationRepository();
    }

    /**
     * 授权主体的令牌信息填充入服务实现查询到的用户信息
     *
     * @param authorization 授权主体
     * @param userDetail    查询到的用户
     */
    protected void tokenGenerate(Authorization authorization, UserDetail userDetail) {
        // 保存 token 信息
        AccessToken accessToken = new AccessToken();
        BeanUtils.copyProperties(userDetail, accessToken);
        authorization.setAccessToken(accessToken);
        authorization.setPermissions(userDetail.getPermissions());
    }


    // endregion
}
